import { defaultInterface, optionsInterface, setDataKeyInterface, versionEnum } from '../types'
const MouseEventList: string[] = ['click', 'dblclick', 'contextmenu', 'mousedown', 'mouseup', 'mouseenter', 'mouseout', 'mouseover']
const DataKey = 'target-key'
class Bpoint {
  private version?: string
  private data: optionsInterface
  constructor(options: optionsInterface) {
    this.data = Object.assign(this.initOptions(), options)
    this.install()
  }
  private initOptions() {
    // 覆盖原有的history的pushState和replaceState方法
    window.history['pushState'] = createHistoryEvent("pushState")
    window.history['replaceState'] = createHistoryEvent('replaceState')
    // 默认的值,如果用户没填的话
    this.version = versionEnum.version
    return <defaultInterface>{
      version: this.version,
      historyPlugin: false,
      hashPlugin: false,
      domPlugin: false,
      errorPlugin: false,
    }
  }
  private install() {
    if (this.data.historyPlugin) {
      // 如果开起来hash路由自动上报,那么就需要监听history的路由改变方式
      this.captureEvent(['pushState', 'replaceState', 'popState'], 'history-pv')
    }
    if (this.data.hashPlugin) {
      this.captureEvent(['hashchange'], 'hash-pv')
    }
    if (this.data.domPlugin || (this.data.domPlugin as any).is) {
      this.targetKeyReport()
    }
    if (this.data.errorPlugin) {
      this.errPluginHandlers()
    }
  }
  /**
   * 暴露自定义uuid的接口
   * @param uuid string
   */
  public setUserId<T extends defaultInterface['uuid']>(uuid: T) {
    this.data.uuid = uuid
  }
  /**
   * 暴露自定义字段的接口
   * @param extra Record<string, any>
   */
  public setExtra<T extends defaultInterface['extra']>(extra: T) {
    this.data.extra = extra
  }
  /**
   * 暴露自定义触发的接口
   * @param data 
   */
  public setDataKey<T extends setDataKeyInterface>(data: T) {
    this.reportService(data)
  }
  private captureEvent<T>(MouseEventList: string[], targetKey: string, data?: T) {
    MouseEventList.forEach(envent => {
      window.addEventListener(envent, () => {
        this.reportService({ envent, targetKey, data })
      })
    })
  }

  private errPluginHandlers() {
    this.errorEvent()
    this.promiseEvent()
  }
  private errorEvent() {
    window.addEventListener('error', (e) => {
      this.setDataKey({ event: 'error', targetKey: 'message', message: e.message })
    })
  }
  private promiseEvent() {
    // 捕获promise的失败
    window.addEventListener('unhandledrejection', (e) => {
      e.promise.catch((error) => {
        this.setDataKey({ event: 'reject', targetKey: 'promise', message: error })
      })
    })
  }
  private targetKeyReport() {
    let mouseEnentList: string[]
    if ((this.data.domPlugin as any)?.options && (this.data.domPlugin as any)?.options?.length != 0) {
      mouseEnentList = (this.data.domPlugin as any)?.options!
    } else {
      mouseEnentList = MouseEventList
    }
    let targetKey: string
    if ((this.data.domPlugin as any)?.dataKey) {
      targetKey = (this.data.domPlugin as any).dataKey
    } else {
      targetKey = DataKey
    }
    mouseEnentList.forEach(event => {
      window.addEventListener(event, (e) => {
        const target = e.target as HTMLElement
        // 获取dom标签上固定的target-key来触发dom上报
        const targetValue = target.getAttribute(targetKey)
        if (targetValue) {
          this.setDataKey({ event, targetKey: targetValue })
        }
      })
    })
  }
  private reportService<T>(data: T) {
    // 这里利用 navigator.sendBeacon 上报，因为页面关闭后XMLReuqest就不会发送了，而navigator.sendBeacon会继续
    // 但是navigator.sendBeacon要上传blob数据
    // 这里对参数做些合并
    const params = Object.assign(this.data, data, { time: new Date().getTime() })
    const headers = {
      type: 'application/x-www-form-urlencoded'
    }
    const bolb = new Blob([JSON.stringify(params)], headers)
    const res = navigator.sendBeacon(this.data.reqUrl, bolb)
  }
}


function createHistoryEvent<T extends keyof History>(type: T) {
  const origin = history[type]
  return function (this: any) {
    // 订阅派发,改写history的pushState和replaceState方法让其可以被监听
    const res = origin.apply(this, arguments)
    var e = new Event(type)
    window.dispatchEvent(e)
    return res
  }
}


export default Bpoint